const formatTime = (date) => {
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()

  return `${[year, month, day].map(formatNumber).join('/')} ${[
    hour,
    minute,
    second
  ]
    .map(formatNumber)
    .join(':')}`
}

const formatNumber = (n) => {
  n = n.toString()
  return n[1] ? n : `0${n}`
}

/**
 * 函数防抖 返回函数连续调用时，空闲时间必须大于或等于 wait，func 才会执行
 * underscore.js 实现
 * @param {function} func 传入函数
 * @param {number} wait 表示时间窗口的间隔
 * @param {boolean} immediate 设置为ture时，调用被包装函数时，立即执行
 * @return {function} 返回客户调用函数
 */
function debounce(func, wait, immediate) {
  let timeout, args, context, timestamp, result

  const later = function later() {
    // 据上一次触发时间间隔
    const last = +new Date() - timestamp

    // 上次被包装函数被调用时间间隔last小于设定时间间隔wait
    if (last < wait && last > 0) {
      timeout = setTimeout(later, wait - last)
    } else {
      timeout = null
      // 如果设定为immediate===true，因为开始边界已经调用过了此处无需调用
      if (!immediate) {
        result = func.apply(context, args)
        if (!timeout) context = args = null
      }
    }
  }

  return function debounced() {
    context = this
    args = arguments
    timestamp = +new Date()
    // 第一次调用该方法时，且不经过防抖函数开始边界调用时，则调用包装函数
    const callNow = immediate && !timeout
    // 如果延时不存在，重新设定延时
    if (!timeout) timeout = setTimeout(later, wait)
    if (callNow) {
      result = func.apply(context, args)
      context = args = null
    }

    return result
  }
}

function cloneDeep(obj) {
  let result
  if (Array.isArray(obj)) {
    result = []
    for (let i = 0; i < obj.length; i++) {
      result.push(cloneDeep(obj[i]))
    }
  } else if (typeof obj === 'object') {
    result = {}
    for (const key in obj) {
      result[key] = cloneDeep(obj[key])
    }
  } else {
    result = obj
  }
  return result
}

function getTimes() {
  const date = new Date()
  let year = padS(date.getFullYear())
  let month = padS(date.getMonth() + 1)
  let day = padS(date.getDate())
  let hour = padS(date.getHours())
  let minute = padS(date.getMinutes())
  let second = padS(date.getSeconds())
  return `${year}/${month}/${day} ${hour}:${minute}:${second}`
}

function padS(time) {
  return time < 10 ? String(time).padStart(2, '0') : time
}

/**
 * 按规则校验规则
 * @param {Object} val 校验参数
 * @param {Array} rules 校验规则 [{  required: true, message: '' }, { validator: validator, message: '' }, { pattern: rules.phone, required: true, message: ''}]
 */
function ruleValidator(val = '', rules = []) {
  return new Promise((resolve, reject) => {
    let result = true
    rules.forEach((rule) => {
      if (rule.required && !val) {
        result = false
        reject(rule.message)
      }
      if (rule.validator && !rule.validator.call(this, val)) {
        result = false
        reject(rule.message)
      }
      if (rule.pattern && !rule.pattern.test(val)) {
        result = false
        reject(rule.message)
      }
    })
    resolve(result)
  })
}

/**
 * 下载文件
 * @param {string} url 下载文件的url
 */
function downloadFile(url) {
  wx.showLoading({
    title: '下载中...',
    mask: true
  })
  wx.downloadFile({
    url: url,
    success: (result) => {
      const option = {}
      const contentDisposition = result.header['Content-Disposition']
      if (
        contentDisposition ||
        (Array.isArray(contentDisposition) && contentDisposition.length > 0)
      ) {
        const fileName = (
          Array.isArray(contentDisposition)
            ? contentDisposition[0]
            : contentDisposition
        )
          .split(';')[1]
          .split('=')[1]
          .replace(/^\"(.*)\"$/g, '$1')
        option.fileType = fileName.replace(/.*\.(.*)/g, '$1')
      }
      wx.openDocument({
        ...option,
        filePath: result.tempFilePath,
        showMenu: true,
        fail: (err) => {
          wx.showModal({
            title: '提示',
            content: '查看失败，请重试',
            showCancel: false
          })
          console.error(err)
        }
      })
    },
    fail: (err) => {
      wx.showModal({
        title: '提示',
        content: '下载失败，请重试',
        showCancel: false
      })
      console.error(err)
    },
    complete: () => {
      wx.hideLoading()
    }
  })
}

/**
 * Creates a function that invokes `func`, with the `this` binding and arguments
 * of the created function, while it's called less than `n` times. Subsequent
 * calls to the created function return the result of the last `func` invocation.
 *
 * @since 3.0.0
 * @category Function
 * @param {number} n The number of calls at which `func` is no longer invoked.
 * @param {Function} func The function to restrict.
 * @returns {Function} Returns the new restricted function.
 * @example
 *
 * jQuery(element).on('click', before(5, addContactToList))
 * // => Allows adding up to 4 contacts to the list.
 */
function before(n, func) {
  let result
  if (typeof func !== 'function') {
    throw new TypeError('Expected a function')
  }
  return function (...args) {
    if (--n > 0) {
      result = func.apply(this, args)
    }
    if (n <= 1) {
      func = undefined
    }
    return result
  }
}
/**
 * Creates a function that is restricted to invoking `func` once. Repeat calls
 * to the function return the value of the first invocation. The `func` is
 * invoked with the `this` binding and arguments of the created function.
 *
 * @since 0.1.0
 * @category Function
 * @param {Function} func The function to restrict.
 * @returns {Function} Returns the new restricted function.
 * @example
 *
 * const initialize = once(createApplication)
 * initialize()
 * initialize()
 * // => `createApplication` is invoked once
 */
function once(func) {
  return before(2, func)
}

module.exports = {
  formatTime,
  debounce,
  getTimes,
  ruleValidator,
  downloadFile: debounce(downloadFile, 300),
  cloneDeep,
  before,
  once
}
